package ru.compscicenter.databases.zaikina.common.model;

import org.json.simple.JSONAware;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import java.util.Collection;
import java.util.HashMap;

public class WordWithReferences implements JSONAware, Mergeable<WordWithReferences>, Cloneable {
    private final Word obj;
    private HashMap<Integer, Integer> references = new HashMap<Integer, Integer>();

    public WordWithReferences(Word obj) {
        this.obj = obj;
    }

    public WordWithReferences(Word obj, HashMap<Integer, Integer> references) {
        this.obj = obj;
        this.references = references;

        if (obj.getPopularity() <= 1) {
            Integer referencesCount = 0;
            for (Integer item : references.values()) {
                referencesCount += item;
            }
            this.obj.setPopularity(referencesCount);
        }
    }

    public Word getObj() {
        return obj;
    }

    public Collection<Integer> getReferences() {
        return references.keySet();
    }

    public void addNewReference(Integer targetId) {
        addNewReference(targetId, 1);
    }

    private void addNewReference(Integer targetId, Integer count) {
        if (count == null) {
            count = 1;
        }

        obj.setPopularity(obj.getPopularity() + count);
        if (references.containsKey(targetId)) {
            count += references.get(targetId);
        }
        references.put(targetId, count);
    }

    public Integer getReferencesCount() {
        return obj.getPopularity();
    }

    public String toJSONString() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("obj", obj);
        jsonObject.put("references", references);
        return jsonObject.toString();
    }

    public static WordWithReferences parse(String json) {
        Word obj = null;
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        //Integer refCount = 0;
        try {
            JSONObject jsonObject = (JSONObject) JSONValue.parse(json);
            String objStr = jsonObject.get("obj").toString();
            obj = Word.parse(objStr);
            String docStr = jsonObject.get("references").toString();
            String arrStr = JSONValue.parse(docStr).toString();

            if (arrStr.length() < 3) {
                return null;
            }
            arrStr = arrStr.substring(1, arrStr.length() - 1);
            String[] args = arrStr.split(",");
            for (String str : args) {
                String[] subArgs = str.split(":");
                if (subArgs.length == 2 && subArgs[0].length() > 2) {
                    subArgs[0] = subArgs[0].substring(1, subArgs[0].length() - 1);
                }
                map.put(Integer.parseInt(subArgs[0]), Integer.parseInt(subArgs[1]));
            }

        } catch (Exception ex) {
            if (obj == null) {
                return null;
            }
        }
        return new WordWithReferences(obj, map);
    }

    private void updateReference(Integer targetId, Integer count) {
        references.put(targetId, count);
    }

    public void removeReference(Integer targetId) {
        removeReference(targetId, Integer.MAX_VALUE);
    }

    private void removeReference(Integer targetId, Integer count) {
        Integer countBefore = 0;
        Integer countAfter = 0;
        if (references.containsKey(targetId)) {
            countBefore = references.get(targetId);
            if (count < countBefore) {
                countAfter = (countBefore - count);
            }
        }
        references.put(targetId, countAfter);
        obj.setPopularity(obj.getPopularity() - (countBefore - countAfter));
    }

    public void mergeWith(WordWithReferences param) {
        if (obj.getKey().equals(param.obj.getKey())) {
            for (Integer id : param.references.keySet()) {
                addNewReference(id, references.get(id));
            }
        }
    }

    public void subtract(WordWithReferences param) {
        if (obj.getKey().equals(param.obj.getKey())) {
            for (Integer id : param.references.keySet()) {
                removeReference(id, references.get(id));
            }
        }
    }

    public void update(WordWithReferences param) {
        if (obj.getKey().equals(param.obj.getKey())) {
            for (Integer id : param.references.keySet()) {
                updateReference(id, references.get(id));
            }
        }
    }

    @Override
    public boolean equals(Object item) {
        if (item instanceof WordWithReferences) {
            WordWithReferences param = (WordWithReferences) item;
            return param.obj.equals(obj) && param.references.equals(references);
        }
        return super.equals(item);
    }

    public WordWithReferences clone() {
        Word wordCopy = obj.clone();
        HashMap<Integer, Integer> referencesCopy = new HashMap<Integer, Integer>();
        for (Integer key : references.keySet()) {
            referencesCopy.put(new Integer(key.intValue()), new Integer(references.get(key).intValue()));
        }
        return new WordWithReferences(wordCopy, referencesCopy);
    }
}
